package com.smartalarmclock.Model;

import java.util.HashSet;
import java.util.Hashtable;
import java.util.Set;
import java.util.Map.Entry;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;

import com.smartalarmclock.Model.Reference.DeleteMethod;
import com.smartalarmclock.Model.Exceptions.DataEntityDoesNotExistException;
import com.smartalarmclock.Model.Exceptions.InvalidDeleteDataEntityOperationException;
import com.smartalarmclock.Model.Exceptions.InvalidNullReferenceOnDeleteException;
import com.smartalarmclock.Model.Exceptions.InvalidReferenceToRemoveException;
import com.smartalarmclock.Model.Exceptions.InvalidSaveDataEntityOperationException;
import com.smartalarmclock.Model.Exceptions.ReferenceCannotBeNullException;
import com.smartalarmclock.Model.Exceptions.VIPAlreadyExistsException;
import com.smartalarmclock.Model.ITimeFrame.TimeInWeek;

final class VIP extends RulesItem implements IVIP {

	// Database contacts
	
	public static final String VIPTableName = "VIP";	
	private static final String c_VIPColumnNameContact = "Contact";
	
	private static final String[] c_QueryAllColumnsVIP = new String[] {	c_ColumnNameId, c_VIPColumnNameContact };
	
	// Members
	
	private final SingleReference<IContact> m_ContactReference;
	
	// Constructors
	
	/**
	 * Constructor
	 * @param alarmClock the alarm clock
	 * @param matchingAutoResponse the matching auto response
	 * @param contact the contact of the VIP
	 */
	protected VIP(IAlarmClock alarmClock, IAutoResponse matchingAutoResponse, IContact contact) {
		super(alarmClock, matchingAutoResponse);
		m_ContactReference = new SingleReference<IContact>(this, contact, false, Contact.class, DeleteMethod.Delete);
	}
	
	/**
	 * Constructor
	 * @param id the id of the VIP
	 * @param alarmClock the alarm clock
	 * @param matchingAutoResponse the matching auto response
	 * @param contact the contact of the VIP
	 */
	private VIP(int id, IAlarmClock alarmClock, IAutoResponse matchingAutoResponse, IContact contact) {
		super(id, alarmClock, matchingAutoResponse);
		m_ContactReference = new SingleReference<IContact>(this, contact, false, Contact.class, DeleteMethod.Delete);
	}
	
	// IVIP

	@Override
	public IContact GetContact() {
		return m_ContactReference.GetReference();
	}
	
	// Public Methods
	
	/**
	 * @return the CREATE TABLE statement of the VIP table
	 */
	static String GetSqlStatementCreateVIPTable() {
		return "CREATE TABLE " + VIPTableName + " (" +
				c_ColumnNameId + 			" INTEGER PRIMARY KEY, " +
				c_VIPColumnNameContact +	" INTEGER NOT NULL, " +
				"FOREIGN KEY(" + c_VIPColumnNameContact + ") REFERENCES " + Contact.ContactTableName + "(" + c_ColumnNameId + "))";
	}
	
	/**
	 * @return the DROP TABLE statement of the VIP table
	 */
	static String GetSqlStatementDropVIPTable() {
		return "DROP TABLE IF EXISTS " + VIPTableName;
	}
	
	/**
	 * Queries for a VIP with the given id
	 * @param context the context of the request
	 * @param id the id to query
	 * @return the VIP with the given id
	 * @throws DataEntityDoesNotExistException if the VIP does not exist
	 */
	static VIP GetVIPById(Context context, int id) throws DataEntityDoesNotExistException {
		return GetSingleVIP(context, c_ColumnNameId + "=" + id);
	}
	
	/**
	 * Queries for a VIP with the given id
	 * @param context the context of the request
	 * @param id the id to query
	 * @return the VIP with the given id, or null if does not exist
	 */
	static VIP TryGetVIPById(Context context, int id) {
		try {
			return GetVIPById(context, id);
		} catch (DataEntityDoesNotExistException e) {
			return null;
		}
	}
	
	/**
	 * Queries for a VIP with the given contact
	 * @param context the context of the request
	 * @param contact the contact to query
	 * @return the VIP with the given contact
	 * @throws DataEntityDoesNotExistException if the VIP does not exist
	 */
	static VIP GetVIPByContact(Context context, IContact contact) throws DataEntityDoesNotExistException {
		return GetSingleVIP(context, c_VIPColumnNameContact + "=" + contact.GetId());
	}
	
	/**
	 * Queries for a VIP with the given contact
	 * @param context the context of the request
	 * @param contact the contact to query
	 * @return the VIP with the given contact, or null if does not exist
	 */
	static VIP TryGetVIPByContact(Context context, IContact contact) {
		try {
			return GetVIPByContact(context, contact);
		} catch (DataEntityDoesNotExistException e) {
			return null;
		}
	}
	
	/**
	 * Queries the DB for all VIPs including time frames matching a time range
	 * @param context the context of the request
	 * @param contact the contact to query
	 * @return a collection of VIPs containing the given time range
	 */
	static Set<IVIP> GetVIPsByTimeRange(Context context, TimeInWeek startTime, TimeInWeek endTime) {
		Set<ITimeFrame> timeFramesByRange = new TimeFrameFactory().GetTimeFramesByTimeRange(context, startTime, endTime);
		HashSet<IVIP> vips = new HashSet<IVIP>();
		
		int rulesItemId = 0;
		IVIP vip = null;
		for (ITimeFrame timeFrame : timeFramesByRange) {
			try {
				rulesItemId = GetRulesItemIdByTimeFrame(context, timeFrame);
				vip = TryGetVIPById(context, rulesItemId);
			} catch (DataEntityDoesNotExistException e) { }
			
			if (vip != null) {
				vips.add(vip);
			}
		}
		
		return vips;
	}
	
	/**
	 * Queries for a VIP with the matching alarm clock
	 * @param context the context of the request
	 * @param alarmClock the alarm clock to query for
	 * @return the matching VIP.
	 * @throws DataEntityDoesNotExistException if the VIP does not exist
	 */
	static VIP GetVIPByAlarmClock(Context context, IAlarmClock alarmClock) throws DataEntityDoesNotExistException {
		SQLiteDatabase db = OpenDBForReading(context);
		
		Cursor rulesItemCursor = GetRulesItemValuesByAlarmClock(db, alarmClock);
		int rulesItemId = rulesItemCursor.getInt(rulesItemCursor.getColumnIndex(c_ColumnNameId));
		IAutoResponse matchingAutoResponse = GetMatchingAutoResponseFromCursor(context, rulesItemCursor);
		rulesItemCursor.close();
		
		return GetVIPById(context, rulesItemId, alarmClock, matchingAutoResponse);
	}
	
	/**
	 * Queries for a VIP with the matching alarm clock
	 * @param context the context of the request
	 * @param alarmClock the alarm clock to query for
	 * @return the matching VIP, or null if does not exist
	 */
	static VIP TryGetVIPByAlarmClock(Context context, IAlarmClock alarmClock) {
		try {
			return GetVIPByAlarmClock(context, alarmClock);
		} catch (DataEntityDoesNotExistException e) {
			return null;
		}
	}
	
	/**
	 * Queries for a VIP with the matching auto response
	 * @param context the context of the request
	 * @param matchingAutoResponse the matching response to query
	 * @return the matching VIP.
	 * @throws DataEntityDoesNotExistException if the VIP does not exist
	 */
	static VIP GetVIPByMatchingAutoResponse(Context context, IAutoResponse matchingAutoResponse) throws DataEntityDoesNotExistException {
		SQLiteDatabase db = OpenDBForReading(context);
		
		Cursor rulesItemCursor = GetRulesItemValuesByMatchingAutoResponse(db, matchingAutoResponse);
		int rulesItemId = rulesItemCursor.getInt(rulesItemCursor.getColumnIndex(c_ColumnNameId));
		IAlarmClock alarmClock = GetAlarmClockFromCursor(context, rulesItemCursor);
		rulesItemCursor.close();
		db.close();
		
		return GetVIPById(context, rulesItemId, alarmClock, matchingAutoResponse);
	}
	
	/**
	 * Queries for a VIP with the matching auto response
	 * @param context the context of the request
	 * @param matchingAutoResponse the auto response to query for
	 * @return the matching VIP, or null if does not exist.
	 */
	static VIP TryGetVIPByMatchingAutoReponse(Context context, IAutoResponse matchingAutoResponse) {
		try {
			return GetVIPByMatchingAutoResponse(context, matchingAutoResponse);
		} catch (DataEntityDoesNotExistException e) {
			return null;
		}
	}
	
	/**
	 * Queries for a VIP with the matching time frame
	 * @param context the context of the request
	 * @param timeFrame the matching time frame to query
	 * @return the matching VIP.
	 * @throws DataEntityDoesNotExistException if the VIP does not exist
	 */
	static VIP GetVIPByTimeFrame(Context context, ITimeFrame timeFrame) throws DataEntityDoesNotExistException {
		SQLiteDatabase db = OpenDBForReading(context);
		
		Cursor rulesItemCursor = GetRulesItemValuesByTimeFrame(context, db, timeFrame);
		int rulesItemId = rulesItemCursor.getInt(rulesItemCursor.getColumnIndex(c_ColumnNameId));
		rulesItemCursor.close();
		db.close();
		
		return GetVIPById(context, rulesItemId);
	}
	
	/**
	 * Queries for a VIP with the matching time frame
	 * @param context the context of the request
	 * @param timeFrame the matching time frame to query
	 * @return the matching VIP or null if does not exist
	 */
	static VIP TryGetVIPByTimeFrame(Context context, ITimeFrame timeFrame) {
		try {
			return GetVIPByTimeFrame(context, timeFrame);
		} catch (DataEntityDoesNotExistException e) {
			return null;
		}
	}
	
	// Private Methods
	
	/**
	 * Queries for a VIP with the matching id
	 * @param context the context of the request
	 * @param id the id to query for
	 * @param alarmClock the alarm clock instance
	 * @param matchingAutoResponse the matching auto response instance
	 * @return the matching VIP.
	 * @throws DataEntityDoesNotExistException if the VIP does not exist
	 */
	private static VIP GetVIPById(
		Context context,
		int id,
		IAlarmClock alarmClock,
		IAutoResponse matchingAutoResponse) throws DataEntityDoesNotExistException {
		return GetSingleVIP(context, c_ColumnNameId + "=" + id, alarmClock, matchingAutoResponse);
	}
	
	/**
	 * Queries for a single VIP using the where clause
	 * @param context the context of the request
	 * @param whereClause the where clause to use
	 * @return the matching VIP instance.
	 * @throws DataEntityDoesNotExistException if the VIP does not exist or if the matching rules item,
	 * alarm clock or auto responses do not exist.
	 */
	private static VIP GetSingleVIP(Context context, String whereClause) throws DataEntityDoesNotExistException {
		SQLiteDatabase db = OpenDBForReading(context);
		Cursor cursor = db.query(
			VIPTableName,
			c_QueryAllColumnsVIP,
			whereClause,
			null, null, null, null);
		
		try {
			if (!cursor.moveToFirst()) {
				throw new DataEntityDoesNotExistException(Profile.class, whereClause);
			}
			
			return CreateVIPFromCursor(db, context, cursor);
		}
		finally {
			cursor.close();
			db.close();
		}
	}
	
	/**
	 * Queries for a single VIP using the where clause
	 * @param context the context of the request
	 * @param whereClause the where clause to use
	 * @param alarmClock the VIP's alarm clock
	 * @param matchingAutoResponse the VIP's matching auto response
	 * @return the matching VIP instance.
	 * @throws DataEntityDoesNotExistException if the VIP does not exist or if the matching rules item,
	 * alarm clock or auto responses do not exist.
	 */
	private static VIP GetSingleVIP(
		Context context,
		String whereClause,
		IAlarmClock alarmClock,
		IAutoResponse matchingAutoResponse) throws DataEntityDoesNotExistException {
		
		SQLiteDatabase db = OpenDBForReading(context);
		Cursor cursor = db.query(
			VIPTableName,
			c_QueryAllColumnsVIP,
			whereClause,
			null, null, null, null);
		
		try {
			if (!cursor.moveToFirst()) {
				throw new DataEntityDoesNotExistException(Profile.class, whereClause);
			}
			
			return CreateVIPFromCursor(db, context, cursor, alarmClock, matchingAutoResponse);
		}
		finally {
			cursor.close();
			db.close();
		}
	}
	
	/**
	 * Creates a VIP instance using the values in the cursor.
	 * @param context the context of the request
	 * @param cursor the cursor containing the values.
	 * @return the matching VIP instance
	 * @throws DataEntityDoesNotExistException if a referenced entity or the matching rules item do not exist.
	 */
	private static VIP CreateVIPFromCursor(SQLiteDatabase db, Context context, Cursor cursor) throws DataEntityDoesNotExistException {
		int id = cursor.getInt(cursor.getColumnIndex(c_ColumnNameId));
		Cursor rulesItemCursor = GetRulesItemValuesById(db, id);
		if (!rulesItemCursor.moveToNext()) {
			throw new DataEntityDoesNotExistException(RulesItem.class, c_ColumnNameId + "=" + id);
		}
		
		IAutoResponse matchingAutoResponse = GetMatchingAutoResponseFromCursor(context, rulesItemCursor);
		IAlarmClock alarmClock = GetAlarmClockFromCursor(context, rulesItemCursor);
		rulesItemCursor.close();
		
		return CreateVIPFromCursor(db, context, cursor, alarmClock, matchingAutoResponse);
	}
	
	/**
	 * Creates a VIP instance using the values in the cursor.
	 * @param context the context of the request
	 * @param cursor the cursor containing the values.
	 * @param alarmClock the alarm clock instance
	 * @param matchingAutoResponse the matching auto response instance
	 * @return the matching VIP instance
	 * @throws DataEntityDoesNotExistException if a referenced entity or the matching rules item do not exist.
	 */
	private static VIP CreateVIPFromCursor(
		SQLiteDatabase db,
		Context context,
		Cursor cursor,
		IAlarmClock alarmClock,
		IAutoResponse matchingAutoResponse) throws DataEntityDoesNotExistException {
		
		int id = cursor.getInt(cursor.getColumnIndex(c_ColumnNameId));
		int contactId = cursor.getInt(cursor.getColumnIndex(c_VIPColumnNameContact));
		IContact contact = new ContactFactory().TryGetContactById(context, contactId);
		if (contact == null) {
			throw new DataEntityDoesNotExistException(Contact.class, String.format("contact matching the VIP %d", id));
		}
		
		return new VIP(id, alarmClock, matchingAutoResponse, contact);
	}
	
	// IDataEntity

	@Override
	public void Refresh(Context context) throws DataEntityDoesNotExistException {
		VIP tempVIP = GetVIPById(context, GetId());
		Refresh(context, tempVIP);
		
		try {
			m_ContactReference.Set(tempVIP.GetContact());
		} catch (ReferenceCannotBeNullException e) { }
	}
	
	@Override
	public void Save(Context context) throws InvalidSaveDataEntityOperationException {
		if (IsNewInstance() && TryGetVIPByContact(context, m_ContactReference.GetReference()) != null) {
			throw new VIPAlreadyExistsException(m_ContactReference.GetReference());
		}
		
		if (IsUpdated()) {
			// Saving the referenced object before adding it to the VIP in the DB.
			m_ContactReference.SaveReferences(context);
			
			super.Save(context);
		}
	}
	
	@Override
	public void Delete(Context context) throws InvalidDeleteDataEntityOperationException, InvalidSaveDataEntityOperationException {
		super.Delete(context);
		
		// Deleting reference if needed.
		m_ContactReference.OnDeleteReferencingEntity(context);
	}
	
	// IReferencingDataEntity
	
	@Override
	public boolean IsReferenced(Context context, IReferencedDataEntity referencedEntity) {
		return super.IsReferenced(context, referencedEntity) ||
			referencedEntity == m_ContactReference.GetReference();
	}

	@Override
	public boolean CanRemoveReference(Context context, IReferencedDataEntity referencedEntity) {
		// If referenced by the rules item and can be removed then true,
		// otherwise, it is either not referenced at all or the contact which in both cases
		// cannot be removed.
		return super.CanRemoveReference(context, referencedEntity);
	}

	@Override
	public void RemoveReference(Context context, IReferencedDataEntity referencedEntity) throws InvalidNullReferenceOnDeleteException, InvalidReferenceToRemoveException {
		try {
			super.RemoveReference(context, referencedEntity);
		} catch (InvalidReferenceToRemoveException e) {
			// The reference is not referenced by the rules item so probably by the VIP
			if (!IsReferenced(context, referencedEntity)) {
				throw new InvalidReferenceToRemoveException(getClass(), GetId(), referencedEntity.getClass(), referencedEntity.GetId());
			}
			
			if (!CanRemoveReference(context, referencedEntity)) {
				throw new InvalidNullReferenceOnDeleteException(getClass(), GetId(), referencedEntity.getClass(), referencedEntity.GetId());
			}
			
			// Either the reference is a contact which cannot be removed or the reference
			// is a rules item reference, in which case it would have never got here.
		}
	}
	
	// DataEntity implementation
	
	@Override
	protected Hashtable<String, ContentValues> GetRecordValues() {
		Hashtable<String, ContentValues> valuesTable = new Hashtable<String, ContentValues>();
		for (Entry<String, ContentValues> entry : super.GetRecordValues().entrySet()) {
			valuesTable.put(entry.getKey(), entry.getValue());
		}
		
		ContentValues values = new ContentValues();
		values.put(c_VIPColumnNameContact, m_ContactReference.GetReference().GetId());
		valuesTable.put(VIPTableName, values);
		
		return valuesTable;
	}
	
	@Override
	protected Set<String> GetTableNames() {
		HashSet<String> tableNames = new HashSet<String>();
		for (String tableName : super.GetTableNames()) {
			tableNames.add(tableName);
		}
		
		tableNames.add(VIPTableName);
		return tableNames;
	}
}
